home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Blender 2.49b / blender-2.49b-windows.exe / $_4_ / .blender / scripts / mesh_cleanup.py < prev    next >
Text File  |  2009-08-31  |  13KB  |  456 lines

  1. #!BPY
  2. """
  3. Name: 'Clean Meshes'
  4. Blender: 245
  5. Group: 'Mesh'
  6. Tooltip: 'Clean unused data from all selected mesh objects.'
  7. """
  8.  
  9. __author__ = "Campbell Barton aka ideasman42"
  10. __url__ = ["www.blender.org", "blenderartists.org", "www.python.org"]
  11. __version__ = "0.1"
  12. __bpydoc__ = """\
  13. Clean Meshes
  14.  
  15. Cleans unused data from selected meshes
  16. """
  17.  
  18. # ***** BEGIN GPL LICENSE BLOCK *****
  19. #
  20. # Script copyright (C) Campbell J Barton
  21. #
  22. # This program is free software; you can redistribute it and/or
  23. # modify it under the terms of the GNU General Public License
  24. # as published by the Free Software Foundation; either version 2
  25. # of the License, or (at your option) any later version.
  26. #
  27. # This program is distributed in the hope that it will be useful,
  28. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  29. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  30. # GNU General Public License for more details.
  31. #
  32. # You should have received a copy of the GNU General Public License
  33. # along with this program; if not, write to the Free Software Foundation,
  34. # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  35. #
  36. # ***** END GPL LICENCE BLOCK *****
  37. # --------------------------------------------------------------------------
  38.  
  39.  
  40. from Blender import *
  41. import bpy
  42. from Blender.Mathutils import TriangleArea
  43.  
  44. import Blender
  45. import BPyMesh
  46. dict2MeshWeight= BPyMesh.dict2MeshWeight
  47. meshWeight2Dict= BPyMesh.meshWeight2Dict
  48.  
  49. def rem_free_verts(me):
  50.     vert_users= [0] * len(me.verts)
  51.     for f in me.faces:
  52.         for v in f:
  53.             vert_users[v.index]+=1
  54.     
  55.     for e in me.edges:
  56.         for v in e: # loop on edge verts
  57.             vert_users[v.index]+=1
  58.     
  59.     verts_free= [i for i, users in enumerate(vert_users) if not users]
  60.     
  61.     if verts_free:
  62.         pass
  63.         me.verts.delete(verts_free)
  64.     return len(verts_free)
  65.     
  66. def rem_free_edges(me, limit=None):
  67.     ''' Only remove based on limit if a limit is set, else remove all '''
  68.     
  69.     edgeDict= {} # will use a set when python 2.4 is standard.
  70.     
  71.     for f in me.faces:
  72.         for edkey in f.edge_keys:
  73.             edgeDict[edkey] = None
  74.     
  75.     edges_free= []
  76.     for e in me.edges:
  77.         if not edgeDict.has_key(e.key):
  78.             edges_free.append(e)
  79.     
  80.     if limit != None:
  81.         edges_free= [e for e in edges_free if e.length <= limit]
  82.     
  83.     me.edges.delete(edges_free)
  84.     return len(edges_free)
  85.  
  86. def rem_area_faces(me, limit=0.001):
  87.     ''' Faces that have an area below the limit '''
  88.     rem_faces= [f for f in me.faces if f.area <= limit]
  89.     if rem_faces:
  90.         me.faces.delete( 0, rem_faces )
  91.     return len(rem_faces)
  92.  
  93. def rem_perimeter_faces(me, limit=0.001):
  94.     ''' Faces whos combine edge length is below the limit '''
  95.     def faceEdLen(f):
  96.         v= f.v
  97.         if len(v) == 3:
  98.             return\
  99.             (v[0].co-v[1].co).length +\
  100.             (v[1].co-v[2].co).length +\
  101.             (v[2].co-v[0].co).length
  102.         else: # 4
  103.             return\
  104.             (v[0].co-v[1].co).length +\
  105.             (v[1].co-v[2].co).length +\
  106.             (v[2].co-v[3].co).length +\
  107.             (v[3].co-v[0].co).length
  108.     rem_faces= [f for f in me.faces if faceEdLen(f) <= limit]
  109.     if rem_faces:
  110.         me.faces.delete( 0, rem_faces )
  111.     return len(rem_faces)
  112.  
  113. def rem_unused_materials(me):
  114.     materials= me.materials
  115.     len_materials= len(materials)
  116.     if len_materials < 2:
  117.         return 0
  118.         
  119.     rem_materials= 0
  120.     
  121.     material_users= dict( [(i,0) for i in xrange(len_materials)] )
  122.     
  123.     for f in me.faces:
  124.         f_mat = f.mat
  125.         # Make sure the face index isnt too big. this happens sometimes.
  126.         if f_mat >= len_materials:
  127.             f_mat = f.mat = 0
  128.         material_users[f_mat] += 1
  129.     
  130.     # mat_idx_subtract= 0
  131.     # reindex_mapping= dict( [(i,0) for i in xrange(len_materials)] )
  132.     
  133.     reindex_mapping_ls = range(len_materials)
  134.     for i in range(len_materials-1, -1, -1):
  135.         if material_users[i] == 0:
  136.             del reindex_mapping_ls[i]
  137.             del materials[i]
  138.             rem_materials+=1
  139.     
  140.     reindex_mapping= {}
  141.     
  142.     for i, mat in enumerate(reindex_mapping_ls):
  143.         reindex_mapping[mat] = i        
  144.     
  145.     for f in me.faces:
  146.         f.mat= reindex_mapping[f.mat]
  147.     
  148.     me.materials= materials
  149.     return rem_materials
  150.  
  151.  
  152. def rem_free_groups(me, groupNames, vWeightDict):
  153.     ''' cound how many vert users a group has and remove unused groups '''
  154.     rem_groups        = 0
  155.     groupUserDict= dict([(group,0) for group in groupNames])
  156.     
  157.     for vertexWeight in vWeightDict:
  158.         for group, weight in vertexWeight.iteritems():
  159.             groupUserDict[group] += 1
  160.     
  161.     i=len(groupNames)
  162.     while i:
  163.         i-=1
  164.         group= groupNames[i]
  165.         if groupUserDict[group] == 0:
  166.             del groupNames[i]
  167.             print '\tremoving, vgroup', group
  168.             rem_groups+=1
  169.     return rem_groups
  170.  
  171. def rem_zero_weights(me, limit, groupNames, vWeightDict):
  172.     ''' remove verts from a group when their weight is zero.'''
  173.     rem_vweight_count= 0
  174.     for vertexWeight in vWeightDict:
  175.         items= vertexWeight.items()
  176.         for group, weight in items:
  177.             if weight < limit:
  178.                 del vertexWeight[group]
  179.                 rem_vweight_count+= 1
  180.  
  181.     return rem_vweight_count
  182.  
  183.     
  184. def normalize_vweight(me, groupNames, vWeightDict):
  185.     for vertexWeight in vWeightDict:
  186.         unit= 0.0
  187.         for group, weight in vertexWeight.iteritems():
  188.             unit+= weight
  189.         
  190.         if unit != 1.0 and unit != 0.0:
  191.             for group, weight in vertexWeight.iteritems():
  192.                 vertexWeight[group]= weight/unit
  193.  
  194. def isnan(f):
  195.     fstring = str(f).lower()
  196.     if 'nan' in fstring:
  197.         return True
  198.     if 'inf' in fstring:
  199.         return True
  200.     
  201.     return False
  202.  
  203. def fix_nan_verts__internal(me):
  204.     rem_nan = 0
  205.     for v in me.verts:
  206.         co = v.co
  207.         for i in (0,1,2):
  208.             if isnan(co[i]):
  209.                 co[i] = 0.0
  210.                 rem_nan += 1
  211.     return rem_nan
  212.  
  213. def fix_nan_verts(me):
  214.     rem_nan = 0
  215.     key = me.key
  216.     if key:
  217.         # Find the object, and get a mesh thats thinked to the oblink.
  218.         # this is a bit crap but needed to set the active key.
  219.         me_oblink = None
  220.         for ob in bpy.data.objects:
  221.             me_oblink = ob.getData(mesh=1)
  222.             if me_oblink == me:
  223.                 me = me_oblink
  224.                 break
  225.         if not me_oblink:
  226.             ob = None
  227.     
  228.     if key and ob:
  229.         blocks = key.blocks
  230.         # print blocks
  231.         orig_pin = ob.pinShape
  232.         orig_shape = ob.activeShape
  233.         orig_relative = key.relative
  234.         ob.pinShape = True
  235.         for i, block in enumerate(blocks):
  236.             ob.activeShape = i+1
  237.             ob.makeDisplayList()
  238.             rem_nan += fix_nan_verts__internal(me)
  239.             me.update(block.name) # get the new verts
  240.         ob.pinShape    = orig_pin
  241.         ob.activeShape = orig_shape
  242.         key.relative = orig_relative
  243.         
  244.     else: # No keys, simple operation
  245.         rem_nan = fix_nan_verts__internal(me)
  246.     
  247.     return rem_nan
  248.  
  249. def fix_nan_uvs(me):
  250.     rem_nan = 0
  251.     if me.faceUV:
  252.         orig_uvlayer = me.activeUVLayer
  253.         for uvlayer in me.getUVLayerNames():
  254.             me.activeUVLayer = uvlayer
  255.             for f in me.faces:
  256.                 for uv in f.uv:
  257.                     for i in (0,1):
  258.                         if isnan(uv[i]):
  259.                             uv[i] = 0.0
  260.                             rem_nan += 1
  261.         me.activeUVLayer = orig_uvlayer
  262.     return rem_nan
  263.  
  264.  
  265. def has_vcol(me):
  266.     for f in me.faces:
  267.         for col in f.col:
  268.             if not (255 == col.r == col.g == col.b):
  269.                 return True
  270.     return False
  271.  
  272. def rem_white_vcol_layers(me):
  273.     vcols_removed = 0
  274.     if me.vertexColors:
  275.         for col in me.getColorLayerNames():
  276.             me.activeColorLayer = col
  277.             if not has_vcol(me): 
  278.                 me.removeColorLayer(col)
  279.                 vcols_removed += 1
  280.     
  281.     return vcols_removed
  282.  
  283.  
  284. def main():    
  285.     sce= bpy.data.scenes.active
  286.     obsel= list(sce.objects.context)
  287.     actob= sce.objects.active
  288.     
  289.     is_editmode= Window.EditMode()
  290.     
  291.     # Edit mode object is not active, add it to the list.
  292.     if is_editmode and (not actob.sel):
  293.         obsel.append(actob)
  294.     
  295.     
  296.     #====================================#
  297.     # Popup menu to select the functions #
  298.     #====================================#
  299.     
  300.     CLEAN_ALL_DATA= Draw.Create(0)
  301.     CLEAN_VERTS_FREE= Draw.Create(1)
  302.     CLEAN_EDGE_NOFACE= Draw.Create(0)
  303.     CLEAN_EDGE_SMALL= Draw.Create(0)
  304.     CLEAN_FACE_PERIMETER= Draw.Create(0)
  305.     CLEAN_FACE_SMALL= Draw.Create(0)
  306.     
  307.     CLEAN_MATERIALS= Draw.Create(0)
  308.     CLEAN_WHITE_VCOL_LAYERS= Draw.Create(0)
  309.     CLEAN_GROUP= Draw.Create(0)
  310.     CLEAN_VWEIGHT= Draw.Create(0)
  311.     CLEAN_WEIGHT_NORMALIZE= Draw.Create(0)
  312.     limit= Draw.Create(0.01)
  313.     
  314.     CLEAN_NAN_VERTS= Draw.Create(0)
  315.     CLEAN_NAN_UVS= Draw.Create(0)
  316.     
  317.     # Get USER Options
  318.     
  319.     pup_block= [\
  320.     ('Verts: free', CLEAN_VERTS_FREE, 'Remove verts that are not used by an edge or a face.'),\
  321.     ('Edges: free', CLEAN_EDGE_NOFACE, 'Remove edges that are not in a face.'),\
  322.     ('Edges: short', CLEAN_EDGE_SMALL, 'Remove edges that are below the length limit.'),\
  323.     ('Faces: small perimeter', CLEAN_FACE_PERIMETER, 'Remove faces below the perimeter limit.'),\
  324.     ('Faces: small area', CLEAN_FACE_SMALL, 'Remove faces below the area limit (may remove faces stopping T-face artifacts).'),\
  325.     ('limit: ', limit, 0.001, 1.0, 'Limit for the area and length tests above (a higher limit will remove more data).'),\
  326.     ('Material Clean', CLEAN_MATERIALS, 'Remove unused materials.'),\
  327.     ('Color Layers', CLEAN_WHITE_VCOL_LAYERS, 'Remove vertex color layers that are totaly white'),\
  328.     ('VGroup Clean', CLEAN_GROUP, 'Remove vertex groups that have no verts using them.'),\
  329.     ('Weight Clean', CLEAN_VWEIGHT, 'Remove zero weighted verts from groups (limit is zero threshold).'),\
  330.     ('WeightNormalize', CLEAN_WEIGHT_NORMALIZE, 'Make the sum total of vertex weights accross vgroups 1.0 for each vertex.'),\
  331.     'Clean NAN values',\
  332.     ('NAN Verts', CLEAN_NAN_VERTS, 'Make NAN or INF verts (0,0,0)'),\
  333.     ('NAN UVs', CLEAN_NAN_UVS, 'Make NAN or INF UVs (0,0)'),\
  334.     '',\
  335.     ('All Mesh Data', CLEAN_ALL_DATA, 'Warning! Operate on ALL mesh objects in your Blend file. Use with care'),\
  336.     ]
  337.     
  338.     if not Draw.PupBlock('Clean Selected Meshes...', pup_block):
  339.         return
  340.     
  341.     CLEAN_VERTS_FREE= CLEAN_VERTS_FREE.val
  342.     CLEAN_EDGE_NOFACE= CLEAN_EDGE_NOFACE.val
  343.     CLEAN_EDGE_SMALL= CLEAN_EDGE_SMALL.val
  344.     CLEAN_FACE_PERIMETER= CLEAN_FACE_PERIMETER.val
  345.     CLEAN_FACE_SMALL= CLEAN_FACE_SMALL.val
  346.     CLEAN_MATERIALS= CLEAN_MATERIALS.val
  347.     CLEAN_WHITE_VCOL_LAYERS= CLEAN_WHITE_VCOL_LAYERS.val
  348.     CLEAN_GROUP= CLEAN_GROUP.val
  349.     CLEAN_VWEIGHT= CLEAN_VWEIGHT.val
  350.     CLEAN_WEIGHT_NORMALIZE= CLEAN_WEIGHT_NORMALIZE.val
  351.     limit= limit.val
  352.     CLEAN_ALL_DATA= CLEAN_ALL_DATA.val
  353.     CLEAN_NAN_VERTS= CLEAN_NAN_VERTS.val
  354.     CLEAN_NAN_UVS= CLEAN_NAN_UVS.val
  355.     
  356.     if is_editmode: Window.EditMode(0)
  357.     
  358.     if CLEAN_ALL_DATA:
  359.         if CLEAN_GROUP or CLEAN_VWEIGHT or CLEAN_WEIGHT_NORMALIZE:
  360.             # For groups we need the objects linked to the mesh
  361.             meshes= [ob.getData(mesh=1) for ob in bpy.data.objects if ob.type == 'Mesh' if not ob.lib]
  362.         else:
  363.             meshes= bpy.data.meshes
  364.     else:
  365.         meshes= [ob.getData(mesh=1) for ob in obsel if ob.type == 'Mesh']
  366.     
  367.     tot_meshes = len(meshes) # so we can decrement libdata
  368.     rem_face_count= rem_edge_count= rem_vert_count= rem_material_count= rem_vcol_layer_count= rem_group_count= rem_vweight_count= fix_nan_vcount= fix_nan_uvcount= 0
  369.     if not meshes:
  370.         if is_editmode: Window.EditMode(1)
  371.         Draw.PupMenu('No meshes to clean')
  372.     
  373.     Blender.Window.WaitCursor(1)
  374.     bpy.data.meshes.tag = False
  375.     for me in meshes:
  376.         
  377.         # Dont touch the same data twice
  378.         if me.tag:
  379.             tot_meshes -= 1
  380.             continue
  381.         me.tag = True
  382.         
  383.         if me.lib:
  384.             tot_meshes -= 1
  385.             continue
  386.         
  387.         if me.multires:
  388.             multires_level_orig = me.multiresDrawLevel
  389.             me.multiresDrawLevel = 1
  390.             print 'Warning, cannot perform destructive operations on multires mesh:', me.name
  391.         else:
  392.             if CLEAN_FACE_SMALL:
  393.                 rem_face_count += rem_area_faces(me, limit)
  394.                 
  395.             if CLEAN_FACE_PERIMETER:
  396.                 rem_face_count += rem_perimeter_faces(me, limit)
  397.             
  398.             if CLEAN_EDGE_SMALL: # for all use 2- remove all edges.
  399.                 rem_edge_count += rem_free_edges(me, limit)
  400.             
  401.             if CLEAN_EDGE_NOFACE:
  402.                 rem_edge_count += rem_free_edges(me)
  403.             
  404.             if CLEAN_VERTS_FREE:
  405.                 rem_vert_count += rem_free_verts(me)
  406.         
  407.         if CLEAN_MATERIALS:
  408.             rem_material_count += rem_unused_materials(me)
  409.         
  410.         if CLEAN_WHITE_VCOL_LAYERS:
  411.             rem_vcol_layer_count += rem_white_vcol_layers(me)
  412.         
  413.         if CLEAN_VWEIGHT or CLEAN_GROUP or CLEAN_WEIGHT_NORMALIZE:
  414.             groupNames, vWeightDict= meshWeight2Dict(me)
  415.             
  416.             if CLEAN_VWEIGHT:
  417.                 rem_vweight_count += rem_zero_weights(me, limit, groupNames, vWeightDict)
  418.             
  419.             if CLEAN_GROUP:
  420.                 rem_group_count += rem_free_groups(me, groupNames, vWeightDict)
  421.                 pass
  422.             
  423.             if CLEAN_WEIGHT_NORMALIZE:
  424.                 normalize_vweight(me, groupNames, vWeightDict)
  425.             
  426.             # Copy back to mesh vertex groups.
  427.             dict2MeshWeight(me, groupNames, vWeightDict)
  428.         
  429.         if CLEAN_NAN_VERTS:
  430.             fix_nan_vcount = fix_nan_verts(me)
  431.             
  432.         if CLEAN_NAN_UVS:
  433.             fix_nan_uvcount = fix_nan_uvs(me)
  434.         
  435.         # restore multires.
  436.         if me.multires:
  437.             me.multiresDrawLevel = multires_level_orig
  438.         
  439.     Blender.Window.WaitCursor(0)
  440.     if is_editmode: Window.EditMode(0)
  441.     stat_string= 'Removed from ' + str(tot_meshes) + ' Mesh(es)%t|'
  442.     
  443.     if CLEAN_VERTS_FREE:                            stat_string+= 'Verts: %i|' % rem_vert_count
  444.     if CLEAN_EDGE_SMALL or CLEAN_EDGE_NOFACE:        stat_string+= 'Edges: %i|' % rem_edge_count
  445.     if CLEAN_FACE_SMALL or CLEAN_FACE_PERIMETER:    stat_string+= 'Faces: %i|' % rem_face_count
  446.     if CLEAN_MATERIALS:                                stat_string+= 'Materials: %i|' % rem_material_count
  447.     if CLEAN_WHITE_VCOL_LAYERS:                        stat_string+= 'Color Layers: %i|' % rem_vcol_layer_count
  448.     if CLEAN_VWEIGHT:                                stat_string+= 'VWeights: %i|' % rem_vweight_count
  449.     if CLEAN_GROUP:                                    stat_string+= 'VGroups: %i|' % rem_group_count
  450.     if CLEAN_NAN_VERTS:                                stat_string+= 'Vert Nan Fix: %i|' % fix_nan_vcount
  451.     if CLEAN_NAN_UVS:                                stat_string+= 'UV Nan Fix: %i|' % fix_nan_uvcount
  452.     Draw.PupMenu(stat_string)
  453.     
  454.     
  455. if __name__ == '__main__':
  456.     main()